home *** CD-ROM | disk | FTP | other *** search
Text File | 2009-10-29 | 41.5 KB | 1,051 lines |
- #!/usr/bin/python
-
- # ubuntuone-client-applet - Tray icon applet for managing Ubuntu One
- #
- # Author: Rodney Dawes <rodney.dawes@canonical.com>
- #
- # Copyright 2009 Canonical Ltd.
- #
- # This program is free software: you can redistribute it and/or modify it
- # under the terms of the GNU General Public License version 3, as published
- # by the Free Software Foundation.
- #
- # This program is distributed in the hope that it will be useful, but
- # WITHOUT ANY WARRANTY; without even the implied warranties of
- # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
- # PURPOSE. See the GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License along
- # with this program. If not, see <http://www.gnu.org/licenses/>.
-
- from __future__ import with_statement
-
- import pygtk
- pygtk.require('2.0')
- import gobject
- import gtk
- import pango
- import os
- import subprocess
- import sys
- import gettext
- from ubuntuone import clientdefs
-
- import dbus.service
-
- # pylint: disable-msg=F0401
- import pynotify
-
- from ConfigParser import ConfigParser
- from dbus.exceptions import DBusException
- from dbus.mainloop.glib import DBusGMainLoop
- from ubuntuone.oauthdesktop.main import Login
- from xdg.BaseDirectory import xdg_config_home
- from threading import Lock
- from urllib import quote
-
- from ubuntuone.oauthdesktop.logger import setupLogging
- logger = setupLogging("UbuntuOne.Client.Applet")
-
- DBusGMainLoop(set_as_default=True)
-
- _ = gettext.gettext
- P_ = gettext.ngettext
-
- APPLET_BUS_NAME = "com.ubuntuone.ClientApplet"
- APPLET_CONFIG_NAME = APPLET_BUS_NAME + ".Config"
-
- DBUS_IFACE_NAME = "com.ubuntuone.SyncDaemon"
- DBUS_IFACE_SYNC_NAME = "com.ubuntuone.SyncDaemon.SyncDaemon"
- DBUS_IFACE_STATUS_NAME = "com.ubuntuone.SyncDaemon.Status"
- DBUS_IFACE_CONFIG_NAME = "com.ubuntuone.SyncDaemon.Config"
-
- DBUS_IFACE_AUTH_NAME = "com.ubuntuone.Authentication"
-
- OAUTH_REALM = "https://ubuntuone.com"
- OAUTH_CONSUMER = "ubuntuone"
- BOOKMARK_NAME = "Ubuntu One"
-
- NOTIFY_ICON_SIZE = 48
-
- # Why thank you GTK+ for enforcing style-set and breaking API
- RCSTYLE = """
- style 'dialogs' {
- GtkDialog::action-area-border = 12
- GtkDialog::button-spacing = 6
- GtkDialog::content-area-border = 0
- }
- widget_class '*Dialog*' style 'dialogs'
- """
-
- CONF_FILE = os.path.join(xdg_config_home, "ubuntuone", "ubuntuone-client.conf")
-
-
- def dbus_async(*args):
- """Simple handler to make dbus do stuff async."""
- pass
-
- class AppletMain(object):
- """Main applet process class."""
-
- def __init__(self, *args, **kw):
- """Initializes the child threads and dbus monitor."""
- from twisted.internet import gtk2reactor
- gtk2reactor.install()
- login = Login(dbus.service.BusName(DBUS_IFACE_AUTH_NAME,
- bus=dbus.SessionBus()))
-
- logger.info(_("Starting Ubuntu One client version %s") %
- clientdefs.VERSION)
-
- # Whether or not we are authorized
- self.is_authorized = False
-
- # Load the config, with some defaults if it doesn't exist yet
- if not os.path.isdir(os.path.dirname(CONF_FILE)):
- os.makedirs(os.path.dirname(CONF_FILE))
-
- self.config = ConfigParser()
- self.config.read(CONF_FILE)
-
- if not self.config.has_section("ubuntuone"):
- self.config.add_section("ubuntuone")
-
- if not self.config.has_option("ubuntuone", "show_applet"):
- self.config.set("ubuntuone", "show_applet", "1")
-
- if not self.config.has_option("ubuntuone", "connect"):
- self.config.set("ubuntuone", "connect", "0")
-
- if not self.config.has_option("ubuntuone", "connected"):
- self.config.set("ubuntuone", "connected", "False")
-
- if not self.config.has_option("ubuntuone", "bookmarked"):
- self.config.set("ubuntuone", "bookmarked", "False")
-
- self.show_applet = self.config.getint("ubuntuone", "show_applet")
- self.connect = self.config.getint("ubuntuone", "connect")
- self.connected = self.config.getboolean("ubuntuone", "connected")
-
- if not os.path.exists(CONF_FILE):
- with open(CONF_FILE, "w+b") as f:
- self.config.write(f)
-
- # Handle some DBus signals
- self.__bus = dbus.SessionBus()
- self.__bus.add_signal_receiver(
- handler_function=self.__new_credentials,
- signal_name="NewCredentials",
- dbus_interface=DBUS_IFACE_AUTH_NAME)
- self.__bus.add_signal_receiver(
- handler_function=self.__auth_denied,
- signal_name="AuthorizationDenied",
- dbus_interface=DBUS_IFACE_AUTH_NAME)
- self.__bus.add_signal_receiver(
- handler_function=self.__no_credentials,
- signal_name="NoCredentials",
- dbus_interface=DBUS_IFACE_AUTH_NAME)
- self.__bus.add_signal_receiver(
- handler_function=self.__got_oauth_error,
- signal_name="OAuthError",
- dbus_interface=DBUS_IFACE_AUTH_NAME)
-
- self.__icon = AppletIcon(main=self, config=self.config)
-
- def __new_credentials(self, realm=None, consumer_key=None, sender=None):
- """Signal callback for when we get new credentials."""
- self.is_authorized = True
-
- self.__start_storage_daemon()
- self.add_to_autostart()
-
- self.set_up_desktopcouch_pairing(consumer_key)
-
- if self.connect == 2:
- return
-
- if self.connect == 1 and not self.connected:
- return
-
- try:
- client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
- follow_name_owner_changes=True)
- iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
- iface.connect(reply_handler=dbus_async,
- error_handler=self.sd_dbus_error)
- except DBusException, e:
- self.sd_dbus_error(e)
-
- def __auth_denied(self):
- """Signal callback for when auth was denied by user."""
- self.is_authorized = False
- self.remove_from_autostart()
-
- def quit_error(e):
- """Only log when quit fails."""
- logger.error(_("Quit Error: %s") % e.get_dbus_message())
-
- try:
- client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
- follow_name_owner_changes=True)
- iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
- iface.quit(reply_handler=dbus_async,
- error_handler=quit_error)
- except DBusException, e:
- quit_error(e)
-
- from twisted.internet import reactor
- reactor.stop()
-
- def __no_credentials(self):
- """Signal callback for when no credentials exist in the keyring."""
- self.is_authorized = False
-
- def __got_oauth_error(self, message=None):
- """Signal callback for when an OAuth error occured."""
- def dialog_response(dialog, response):
- """Handle the dialog closing."""
- dialog.destroy()
-
- if message:
- logger.error(message)
- dialog = gtk.Dialog(title=_("Ubuntu One: Error"),
- flags=gtk.DIALOG_NO_SEPARATOR,
- buttons=(gtk.STOCK_CLOSE,
- gtk.RESPONSE_CLOSE))
- dialog.set_default_response(gtk.RESPONSE_CLOSE)
- dialog.set_icon_name("ubuntuone-client")
-
- area = dialog.get_content_area()
-
- hbox = gtk.HBox(spacing=12)
- hbox.set_border_width(12)
- area.pack_start(hbox)
- hbox.show()
-
- image = gtk.Image()
- image.set_from_icon_name("dialog-error", gtk.ICON_SIZE_DIALOG)
- image.set_alignment(0.5, 0.0)
- image.show()
- hbox.pack_start(image, False, False)
-
- vbox = gtk.VBox(spacing=12)
- vbox.show()
- hbox.pack_start(vbox)
-
- label = gtk.Label("<b>%s</b>" % _("Authorization Error"))
- label.set_use_markup(True)
- label.set_alignment(0.0, 0.5)
- label.show()
- vbox.pack_start(label, False, False)
-
- label = gtk.Label(message)
- label.set_line_wrap(True)
- label.set_max_width_chars(64)
- label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
- label.set_alignment(0.0, 0.0)
- label.show()
- vbox.pack_start(label, True, True)
-
- dialog.connect('close', dialog_response, gtk.RESPONSE_CLOSE)
- dialog.connect('response', dialog_response)
-
- dialog.show()
- else:
- logger.error(_("Got an OAuth error with no message."))
-
- def check_for_token(self, do_login=False):
- """Method to check for an existing token."""
- def local_dbus_error(e):
- """Can't talk to ourself?"""
- logger.error(_("Internal Error: %s") % e.get_dbus_message())
-
- try:
- client = self.__bus.get_object(DBUS_IFACE_AUTH_NAME, "/",
- follow_name_owner_changes=True)
- iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)
- iface.maybe_login(OAUTH_REALM, OAUTH_CONSUMER,
- do_login,
- reply_handler=dbus_async,
- error_handler=local_dbus_error)
- except DBusException, e:
- local_dbus_error(e)
- return False
-
- return False
-
- def __start_storage_daemon_maybe(self):
- """Start the storage daemon."""
- try:
- client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
- follow_name_owner_changes=True)
- iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
- iface.get_rootdir(reply_handler=dbus_async,
- error_handler=self.sd_dbus_error)
- except DBusException, e:
- self.sd_dbus_error(e)
- return False
-
- return False
-
- def __start_storage_daemon(self):
- """Need to call dbus from a idle callback"""
- gobject.idle_add(self.__start_storage_daemon_maybe)
-
- def add_to_autostart(self):
- """Add ourself to the autostart config."""
- autostart_entry = """[Desktop Entry]
- Name=Ubuntu One
- Exec=ubuntuone-client-applet
- Icon=ubuntuone-client
- Terminal=false
- Type=Application
- X-Ubuntu-Gettext-Domain=ubuntuone-client
- X-KDE-autostart-after=panel
- X-GNOME-Autostart-enabled=true
- """
- if not os.path.exists(os.path.join(xdg_config_home, "autostart")):
- os.makedirs(os.path.join(xdg_config_home, "autostart"))
-
- file_path = os.path.join(xdg_config_home, "autostart",
- "ubuntuone-client-applet.desktop")
- if not os.path.exists(file_path):
- with open(file_path, "w+") as f:
- f.write(autostart_entry)
-
- def remove_from_autostart(self):
- """Remove ourself from the autostart config."""
- path = os.path.join(xdg_config_home, "autostart",
- "ubuntuone-client-applet.desktop")
- try:
- os.unlink(path)
- except OSError:
- pass
-
- def set_up_desktopcouch_pairing(self, consumer_key):
- """Add a pairing record between desktopcouch and Ubuntu One"""
- try:
- from desktopcouch.pair.couchdb_pairing.couchdb_io import \
- put_static_paired_service, PAIRED_SERVER_RECORD_TYPE
- from desktopcouch.records.server import CouchDatabase
- except ImportError:
- # desktopcouch is not installed
- logger.debug(_("Not adding desktopcouch pairing since"
- " desktopcouch is not installed"))
- return
- # Check whether there is already a record of the Ubuntu One service
- db = CouchDatabase("management", create=True)
- if not db.view_exists("ubuntu_one_pair_record","ubuntu_one_pair_record"):
- map_js = """function(doc) {
- if (doc.service_name == "ubuntuone") {
- if (doc.application_annotations &&
- doc.application_annotations["Ubuntu One"] &&
- doc.application_annotations["Ubuntu One"]["private_application_annotations"] &&
- doc.application_annotations["Ubuntu One"]["private_application_annotations"]["deleted"]) {
- emit(doc._id, 1);
- } else {
- emit(doc._id, 0)
- }
- }
- }"""
- db.add_view("ubuntu_one_pair_record", map_js, None,
- "ubuntu_one_pair_record")
- results = db.execute_view("ubuntu_one_pair_record",
- "ubuntu_one_pair_record")
- found = False
- deleted = False
- # Results should contain either one row or no rows
- # If there is one row, its value will be 0, meaning that there is
- # already an Ubuntu One pairing record, or 1, meaning that there
- # was an Ubuntu One pairing record but it has since been unpaired
- # Only create a new record if there is not one already. Specifically,
- # do not add the record if there is a deleted one, as this means
- # that the user explicitly unpaired it!
- for row in results:
- found = True
- if row.value == 1:
- deleted = True
- logger.debug(_("Not adding desktopcouch pairing since"
- " the user has explicitly unpaired with Ubuntu One"))
- else:
- logger.debug(_("Not adding desktopcouch pairing since"
- " we are already paired"))
- if not found:
- put_static_paired_service(None, "ubuntuone")
- logger.debug(_("Pairing desktopcouch with Ubuntu One"))
-
- def main(self):
- """Starts the gtk main loop."""
- from twisted.internet import reactor
- if self.connect != 2 or (self.connect == 1 and self.connected):
- gobject.idle_add(self.check_for_token, True)
-
- reactor.run()
-
- @property
- def authorized(self):
- """Are we authorized?"""
- return self.is_authorized
-
- def sd_dbus_error(self, error):
- """Got an error from DBus."""
- self.__icon.sd_dbus_error(error)
-
-
-
- def do_config_open():
- """Opens the preferences dialog."""
- paths = os.environ["PATH"].split(":")
- dirpath = os.path.join(os.getcwd(), "bin")
- if os.path.isdir(dirpath):
- paths.insert(0, dirpath)
- os.environ["PATH"] = ":".join(paths)
- try:
- ret = subprocess.call(["ubuntuone-client-preferences"])
- except OSError:
- ret = -1
- if ret != 0:
- logger.error(_("Failed to open Ubuntu One preferences"))
-
- def do_xdg_open(path_or_url):
- """Utility method to run xdg-open with path_or_url."""
- ret = subprocess.call(["xdg-open", path_or_url])
- if ret != 0:
- logger.error(_("Failed to run 'xdg-open %s'") % path_or_url)
-
- class AppletIcon(gtk.StatusIcon):
- """
- Custom StatusIcon derived from gtk.StatusIcon which supports
- animated icons and a few other nice things.
- """
-
- def __init__(self, main=None, config=None, *args, **kw):
- """Initializes our custom StatusIcon based widget."""
- super(AppletIcon, self).__init__(*args, **kw)
- # Hide until we get status, to avoid flickering
- self.set_visible(False)
-
- # The AppletMain object
- self.__main = main
-
- # A ConfigParser object that we can poke at
- self.__config = config
- self.__show_when = self.__config.getint("ubuntuone", "show_applet")
-
- self.__managed_dir = None
-
- self.__size = 24
- self.__theme = gtk.icon_theme_get_default()
- iconpath = os.path.abspath(os.path.join(
- os.path.split(os.path.dirname(__file__))[0],
- "data"))
- self.__theme.append_search_path(iconpath)
-
- self.__theme.append_search_path(os.path.sep + os.path.join(
- "usr", "share", "ubuntuone-client", "icons"))
- self.__theme.append_search_path(os.path.sep + os.path.join(
- "usr", "local", "share", "ubuntuone-client", "icons"))
-
- self.set_from_icon_name('ubuntuone-client-offline')
- self.set_tooltip(_("Disconnected"))
- self.connect("popup-menu", self.__popup_menu)
- self.connect("activate", self.__do_action)
-
- self.__size_changed(self, self.__size)
-
- self.__litems = {}
- self.__ritems = {}
- self.__status_menu, self.__config_menu = self.__build_menus()
-
- self.__connected = False
- self.__need_update = False
- self.__fatal_error = False
-
- pynotify.init("Ubuntu One")
-
- # Managing applet visibility
- self.__visible = True
- self.__visible_id = 0
-
- # Up/Dn status
- self.__lock = Lock()
- self.__updating = 0
- self.__total = 0
- self.__last_id = 0
-
- self.__bus = dbus.SessionBus()
-
- # Our own DBus service, for the config to deal with
- self.__service = AppletConfig(icon=self)
-
- # DBus signal handling
- self.__bus.add_signal_receiver(
- handler_function=self.__status_changed,
- signal_name="StatusChanged",
- dbus_interface=DBUS_IFACE_STATUS_NAME)
-
- self.__bus.add_signal_receiver(
- handler_function=self.__queue_changed,
- signal_name="ContentQueueChanged",
- dbus_interface=DBUS_IFACE_STATUS_NAME)
- self.__bus.add_signal_receiver(
- handler_function=self.__transfer_started,
- signal_name="UploadStarted",
- dbus_interface=DBUS_IFACE_STATUS_NAME)
- self.__bus.add_signal_receiver(
- handler_function=self.__transfer_started,
- signal_name="DownloadStarted",
- dbus_interface=DBUS_IFACE_STATUS_NAME)
-
- gobject.idle_add(self.__get_root)
-
- def set_from_icon_name(self, icon):
- """Handle fallbacks for setting our icon."""
- pixbuf = self.__theme.load_icon(icon, self.__size,
- gtk.ICON_LOOKUP_GENERIC_FALLBACK)
- self.set_from_pixbuf(pixbuf)
-
- def set_visibility_config(self, visibility):
- """Update the visibility configuration."""
- self.__show_when = int(visibility)
- self.__config.set("ubuntuone", "show_applet", str(self.__show_when))
- self.update_visibility()
-
- def set_connection_config(self, connect):
- """Update the connection config."""
- self.__config.set("ubuntuone", "connect", str(connect))
-
- def __update_transfer_status(self, done=False):
- """Update the status display."""
- with self.__lock:
- text = _("Updating %(transfers)d of %(total)d files...") % (
- { 'transfers' : self.__updating,
- 'total' : self.__total})
- label = self.__litems["status"].get_child()
- if done:
- self.set_tooltip(_("Files updated."))
- self.set_from_icon_name("ubuntuone-client-idle")
- label.set_text(_("Your files are up to date."))
- else:
- label.set_markup("<i>%s</i>" % text)
-
- def __queue_changed(self, queue):
- """Handle ContentQueueChanged."""
- total = 0
- d = queue.get('Download', None)
- if d is not None:
- total += int(d.get('count', 0))
- d = queue.get('Upload', None)
- if d is not None:
- total += int(d.get('count', 0))
- first = False
- last = False
- self.__visible = True
- self.set_tooltip(_("Updating files..."))
- self.update_visibility()
- with self.__lock:
- if self.__total == 0:
- first = True
- last = False
- if self.__total != 0 and total == 0:
- first = False
- last = True
- self.__total = total + self.__updating
- if first:
- self.set_from_icon_name("ubuntuone-client-updating")
- n = pynotify.Notification(
- _("Updating files..."),
- _("Ubuntu One is now updating your files."))
- pixbuf = self.__theme.load_icon(
- "ubuntuone-client-updating", NOTIFY_ICON_SIZE,
- gtk.ICON_LOOKUP_GENERIC_FALLBACK)
- n.set_icon_from_pixbuf(pixbuf)
- n.show()
- if last:
- if self.__last_id != 0:
- gobject.source_remove(self.__last_id)
- self.__last_id = 0
- self.__last_id = gobject.timeout_add_seconds(
- 15, self.__updating_completed)
-
- def __updating_completed(self):
- """Timeout to avoid multiple started/finished notifications."""
- really_last = False
- n = None
- with self.__lock:
- done = self.__total - self.__updating
- if done == 0:
- really_last = True
- if not really_last:
- return False
-
- with self.__lock:
- n = pynotify.Notification(
- _("Updating Finished"),
- P_("Ubuntu One finished updating %(total)d file.",
- "Ubuntu One finished updating %(total)d files.",
- self.__total) % { 'total' : self.__total })
- self.__total = 0
- self.__updating = 0
- pixbuf = self.__theme.load_icon(
- "ubuntuone-client-updating", NOTIFY_ICON_SIZE,
- gtk.ICON_LOOKUP_GENERIC_FALLBACK)
- n.set_icon_from_pixbuf(pixbuf)
- n.show()
- self.__update_transfer_status(True)
- return False
-
- def __transfer_started(self, path):
- """Handle the started signals."""
- with self.__lock:
- self.__updating += 1
- self.__update_transfer_status()
-
- def update_visibility(self):
- """Update the icon's visibility."""
- if self.__visible_id != 0:
- gobject.source_remove(self.__visible_id)
- self.__visible_id = 0
-
- if (self.__visible and self.__show_when != 2) or self.__fatal_error:
- self.set_visible(True)
- return
-
- if self.__show_when == 2 and not self.__fatal_error:
- self.set_visible(False)
- return
-
- # If the icon is shown, set up a timeout to hide it
- if self.get_visible():
- self.__visible_id = gobject.timeout_add_seconds(
- 30, self.__hide_icon)
-
- def __status_changed(self, status):
- """The sync daemon status changed."""
- if self.__managed_dir is None:
- gobject.idle_add(self.__get_root)
-
- if self.__show_when != 0:
- self.__visible = False
-
- state = status['name']
-
- self.set_tooltip("Ubuntu One")
-
- if self.__fatal_error and state != "UNKNOWN_ERROR":
- # Just blow your nose, and it's fixed, isn't it.
- self.__fatal_error = False
- self.__litems["connect"].set_sensitive(True)
- self.__litems["disconnect"].set_sensitive(True)
-
- if state == "OFFLINE" or state.startswith("INIT") or \
- state.startswith("READY"):
- self.set_from_icon_name("ubuntuone-client-offline")
- self.set_tooltip(_("Disconnected"))
- self.__connected = False
- self.__visible = True
-
- elif state == "CAPABILITIES_MISMATCH":
- self.__connected = False
- self.__visible = True
- # Pop up a notification
- n = pynotify.Notification(
- _("Capabilities Mismatch"),
- _("There was a capabilities mismatch while attempting "
- "to connect to the Ubuntu One server. You may "
- "have installed a newer version of the client, for "
- "which the server does not yet provide support. "
- "A new version of the server should be accessible "
- "soon. Please be patient while we update."))
- pixbuf = self.__theme.load_icon(
- "ubuntuone-client-error", NOTIFY_ICON_SIZE,
- gtk.ICON_LOOKUP_GENERIC_FALLBACK)
- n.set_icon_from_pixbuf(pixbuf)
- n.set_urgency(pynotify.URGENCY_CRITICAL)
- n.show()
- # Set the tooltip and icon on the applet
- self.set_tooltip(_("Capabilities mismatch with server."))
- self.set_from_icon_name("ubuntuone-client-error")
-
- elif state == "IDLE" or state.startswith("READING") or \
- state.startswith("SCANNING"):
- self.__connected = True
- if self.__show_when != 0:
- self.__visible = False
-
- elif state == "AUTH_FAILED":
- self.__stop_syncdaemon()
- self.set_from_icon_name("ubuntuone-client-error")
- self.set_tooltip(_("Authentication failed"))
- self.__connected = False
- self.__visible = True
-
- def reauthorize_error(e):
- """Simple dbus error handler."""
- logger.error(_("Error clearing token: %s") % str(e))
-
- try:
- def token_cleared():
- """Do the next step."""
- if self.__main:
- self.__main.check_for_token(True)
-
- client = self.__bus.get_object(DBUS_IFACE_AUTH_NAME,
- "/", follow_name_owner_changes=True)
- iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)
- iface.clear_token(OAUTH_REALM, OAUTH_CONSUMER,
- reply_handler=token_cleared,
- error_handler=reauthorize_error)
- except DBusException, e:
- reauthorize_error(e)
-
- elif state == "UNKNOWN_ERROR":
- # Disable some menu items
- self.__litems["connect"].set_sensitive(False)
- self.__litems["disconnect"].set_sensitive(False)
- # Change the behavior to file a bug
- if self.__fatal_error:
- return
-
- self.__fatal_error = True
- self.__visible = True
-
- # Pop up a notification
- n = pynotify.Notification(
- "Ubuntu One",
- _("There was a fatal error in Ubuntu One. " +
- "This may be a bug in the software. "
- "Please click on the Ubuntu One icon " +
- "in your panel to report a bug."))
- pixbuf = self.__theme.load_icon(
- "ubuntuone-client-error", NOTIFY_ICON_SIZE,
- gtk.ICON_LOOKUP_GENERIC_FALLBACK)
- n.set_icon_from_pixbuf(pixbuf)
- n.set_urgency(pynotify.URGENCY_CRITICAL)
- n.show()
- # Set the tooltip and icon on the applet
- self.set_tooltip(_("Fatal Error"))
- self.set_from_icon_name("ubuntuone-client-error")
-
- else:
- self.__connected = True
- self.set_from_icon_name("ubuntuone-client-idle")
- if state.startswith("CONNECTING") or \
- state.startswith("START_CONNECTING") or \
- state.startswith("AUTHENTICATING") or \
- state.startswith("CONNECTED") or \
- state.startswith("START_CONNECTED"):
- self.set_from_icon_name("ubuntuone-client-idle")
- self.set_tooltip(_("Connecting"))
- self.__visible = True
-
- self.update_visibility()
-
- if self.__connected:
- self.__litems["connect"].hide()
- self.__litems["disconnect"].show()
- else:
- self.__litems["connect"].show()
- self.__litems["disconnect"].hide()
- self.__config.set("ubuntuone", "connected", self.__connected)
-
- def __hide_icon(self):
- """Timeout to hide tray icon after a period of inactivity."""
- if self.__show_when == 0:
- return False
-
- self.__visible = False
- self.__visible_id = 0
- self.set_visible(False)
- return False
-
- def __get_root(self):
- """Method to get the rootdir from the sync daemon."""
- # Get the managed root directory
- try:
- client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
- follow_name_owner_changes=True)
- except DBusException:
- return False
-
- iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
- def got_root(root):
- """We got the root dir."""
- self.__managed_dir = root
- if os.path.isdir(self.__managed_dir) and \
- os.access(self.__managed_dir,
- os.F_OK | os.R_OK):
- self.__ritems["open"].set_sensitive(True)
- self.__add_to_places()
- else:
- self.__ritems["open"].set_sensitive(False)
-
- def got_err(error):
- """Handle error from the dbus callback."""
- self.sd_dbus_error(error)
- self.__managed_dir = None
- self.__ritems["open"].set_sensitive(False)
-
- iface.get_rootdir(reply_handler=got_root, error_handler=got_err)
-
- # Now get the current status
- try:
- client = self.__bus.get_object(DBUS_IFACE_NAME, "/status",
- follow_name_owner_changes=True)
- iface = dbus.Interface(client, DBUS_IFACE_STATUS_NAME)
- iface.current_status(reply_handler=self.__status_changed,
- error_handler=self.sd_dbus_error)
- except DBusException, e:
- self.sd_dbus_error(e)
- return False
-
- return False
-
- def __build_menus(self):
- """Create the pop-up menu items."""
- # Create the left-click menu
- lmenu = gtk.Menu()
-
- self.__litems["status"] = gtk.MenuItem(
- label=_("Your files are up to date."))
- lmenu.append(self.__litems["status"])
- self.__litems["status"].set_sensitive(False)
- self.__litems["status"].show()
-
- sep = gtk.SeparatorMenuItem()
- lmenu.append(sep)
- sep.show()
-
- self.__litems["connect"] = gtk.ImageMenuItem(
- stock_id=gtk.STOCK_CONNECT)
- lmenu.append(self.__litems["connect"])
- self.__litems["connect"].connect("activate", self.__toggle_state)
- self.__litems["connect"].show()
-
- self.__litems["disconnect"] = gtk.ImageMenuItem(
- stock_id=gtk.STOCK_DISCONNECT)
- lmenu.append(self.__litems["disconnect"])
- self.__litems["disconnect"].connect("activate", self.__toggle_state)
-
- lmenu.show()
-
- # Create the right-click menu
- rmenu = gtk.Menu()
-
- self.__ritems["bug"] = gtk.MenuItem(label=_("_Report a Problem"))
- rmenu.append(self.__ritems["bug"])
- self.__ritems["bug"].connect("activate", self.__report_problem)
- self.__ritems["bug"].show()
-
- self.__ritems["open"] = gtk.MenuItem(label=_("_Open Folder"))
- rmenu.append(self.__ritems["open"])
- self.__ritems["open"].connect("activate", self.__open_folder)
- self.__ritems["open"].set_sensitive(False)
- self.__ritems["open"].show()
-
- self.__ritems["web"] = gtk.MenuItem(label=_("_Go to Web"))
- rmenu.append(self.__ritems["web"])
- self.__ritems["web"].connect("activate", self.__open_website)
- self.__ritems["web"].show()
-
- self.__ritems["config"] = gtk.ImageMenuItem(
- stock_id=gtk.STOCK_PREFERENCES)
- rmenu.append(self.__ritems["config"])
- self.__ritems["config"].connect("activate", self.__open_config)
- self.__ritems["config"].show()
-
- sep = gtk.SeparatorMenuItem()
- rmenu.append(sep)
- sep.show()
-
- self.__ritems["quit"] = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT)
- rmenu.append(self.__ritems["quit"])
- self.__ritems["quit"].connect("activate", self.__quit_applet)
- self.__ritems["quit"].show()
-
- rmenu.show()
-
- return lmenu, rmenu
-
- def __size_changed(self, icon, size, data=None):
- """Callback for when the size changes."""
- if size < 24:
- self.__size = 16
- elif size >= 24 and size < 32:
- self.__size = 24
- elif size >= 32 and size < 48:
- self.__size = 32
- elif size >= 48 and size < 64:
- self.__size = 48
- else:
- self.__size = size
-
- def __popup_menu(self, icon, button, timestamp, data=None):
- """Pops up the context menu for the tray icon."""
- if button == 0:
- self.__status_menu.popup(None, None,
- gtk.status_icon_position_menu,
- button, timestamp, icon)
- else:
- self.__config_menu.popup(None, None,
- gtk.status_icon_position_menu,
- button, timestamp, icon)
-
- def __stop_syncdaemon(self):
- """Tell the syncdaemon to quit."""
- def quit_error(e):
- """Just log and ignore."""
- logger.error(_("Quit Error: %s") % e.get_dbus_message())
-
- try:
- client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
- follow_name_owner_changes=True)
- iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
- iface.quit(reply_handler=dbus_async,
- error_handler=quit_error)
- except DBusException, e:
- quit_error(e)
-
- def __quit_applet(self, menuitem, data=None):
- """Quit the daemon and closes the applet."""
- self.__stop_syncdaemon()
-
- from twisted.internet import reactor
- reactor.stop()
-
- def __toggle_state(self, menuitem, data=None):
- """Connects or disconnects the storage sync process."""
- try:
- client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
- follow_name_owner_changes=True)
- iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
- if self.__connected:
- iface.disconnect(reply_handler=dbus_async,
- error_handler=self.sd_dbus_error)
- else:
- if self.__main and self.__main.authorized is False:
- self.__main.check_for_token(do_login=True)
- iface.connect(reply_handler=dbus_async,
- error_handler=self.sd_dbus_error)
- except DBusException, e:
- self.sd_dbus_error(e)
-
- self.__config.set("ubuntuone", "connected", not self.__connected)
- with open(CONF_FILE, "w+b") as f:
- self.__config.write(f)
-
- def __open_folder(self, data=None):
- """Opens the storage folder in the file manager."""
- if not self.__managed_dir or not os.path.isdir(self.__managed_dir):
- return
-
- folder = "file://%s" % quote(self.__managed_dir)
- do_xdg_open(folder)
-
- def __do_action(self, data=None):
- """Handles the most appropriate action when the icon is clicked."""
- if self.__fatal_error:
- self.__report_problem()
- self.__quit_applet(None)
- return
-
- if self.__need_update:
- do_xdg_open("apt:ubuntuone-storage-protocol?refresh=yes")
- return
-
- # Popup the status menu
- self.emit("popup-menu", 0, gtk.get_current_event_time())
-
- def __report_problem(self, data=None):
- """Runs apport to report a problem against our code."""
- args = ["ubuntu-bug", "ubuntuone-client"]
- ret = subprocess.call(args)
- if ret != 0:
- logger.error(_("Failed to run 'ubuntu-bug'"))
-
- def __open_website(self, data=None, url=None):
- """Opens the one.ubuntu.com web site."""
- if url:
- do_xdg_open(url)
- else:
- do_xdg_open("https://one.ubuntu.com/")
-
-
- def __open_config(self, data=None):
- """Opens the preferences dialog."""
- do_config_open()
-
- def __add_to_places(self):
- """Add the managed directory to the .gtk-bookmarks file."""
- # Only add once
- if self.__config.getboolean("ubuntuone", "bookmarked"):
- return
-
- path = os.path.join(os.path.expanduser("~"), ".gtk-bookmarks")
- with open(path, "a+") as f:
- bookmarks_entry = "file://%s %s\n" % (
- quote(self.__managed_dir), BOOKMARK_NAME)
- in_file = False
- for line in f:
- if line == bookmarks_entry:
- in_file = True
- if not in_file:
- f.write(bookmarks_entry)
-
- self.__config.set("ubuntuone", "bookmarked", "True")
- with open(CONF_FILE, "w+b") as f:
- self.__config.write(f)
-
- def sd_dbus_error(self, error):
- """
- Handle DBus errors for crucial syncdaemon calls,
- and change the applet behavior slightly.
- """
- logger.error(_("DBus Error: %s") % error.get_dbus_message())
- if self.__fatal_error:
- return
-
- self.__fatal_error = True
- self.__status_changed({'name' : 'UNKNOWN_ERROR'})
-
-
- class AppletConfig(dbus.service.Object):
- """DBus Service object"""
-
- def __init__(self, icon, *args, **kwargs):
- """Initialize our magic."""
- self.icon = icon
- self.path = "/config"
- self.bus = dbus.SessionBus()
- bus_name = dbus.service.BusName(APPLET_BUS_NAME,
- bus=self.bus)
- dbus.service.Object.__init__(self, bus_name=bus_name,
- object_path=self.path)
-
- @dbus.service.method(APPLET_CONFIG_NAME,
- in_signature='i', out_signature='')
- def set_visibility_config(self, visibility):
- self.icon.set_visibility_config(visibility)
-
- @dbus.service.method(APPLET_CONFIG_NAME,
- in_signature='i', out_signature='')
- def set_connection_config(self, connect):
- self.icon.set_connection_config(connect)
-
-
- if __name__ == "__main__":
- gettext.bindtextdomain(clientdefs.GETTEXT_PACKAGE, clientdefs.LOCALEDIR)
- gettext.textdomain(clientdefs.GETTEXT_PACKAGE)
-
- # Register DBus service for making sure we run only one instance
- bus = dbus.SessionBus()
- if bus.request_name(APPLET_BUS_NAME, dbus.bus.NAME_FLAG_DO_NOT_QUEUE) == dbus.bus.REQUEST_NAME_REPLY_EXISTS:
- print _("Ubuntu One client applet already running, quitting")
- do_config_open()
- sys.exit(0)
-
- gtk.rc_parse_string(RCSTYLE)
-
- icon = AppletMain()
- icon.main()
-